﻿#include "modelfileparse.h"

#include <QFile>
#include <QTextStream>
#include <QFileInfo>
#include <QDir>
#include <QTextureImage>
#include <Qt3DRender/QTexture>
#include <QColor>
#include <Qt3DExtras/QPhongMaterial>
#include <Qt3DRender/QTexture>

#include <Qt3DRender/QAbstractTexture>
#include <Qt3DExtras/QNormalDiffuseMapMaterial>
#include <Qt3DRender/QTextureImage>
#include <QDiffuseMapMaterial>
#include <QTextureWrapMode>
#include "objheader.h"

ModelFileParse::ModelFileParse(QObject *parent)
    :QObject(parent)
{

}

bool ModelFileParse::parseModelFile(const QString& objFilePath, const QString &mtlFilePath) {
    // 模型文件开始解析信号
    emit parseModelFileStart();

    // 清楚数据，防止多次调用后冲突
    this->m_materialMap.clear();
    this->m_texCoords.clear();
    this->m_normals.clear();
    this->faces.clear();
    this->m_faceGroups.clear();
    this->m_vertexData.clear();
    this->m_indexData.clear();

    // 保存本次的文件路径
    m_mtlFilePath = mtlFilePath;
    m_objFilePath = objFilePath;

    QFile file(objFilePath);
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        // 模型文件解析失败信号
        emit parseModelFileError(1,tr("模型OBJ文件无法打开"));
        qWarning("Failed to open file: %s", qUtf8Printable(objFilePath));
        return false;
    }

    // 默认材质名（防止 usemtl 缺失）
    m_currentMaterial = "default_material";

    // 1.解析模型OBJ文件
    emit parseModelFileDoing(1,1,tr("开始解析模型OBJ文件"));
    QTextStream in(&file);
    while (!in.atEnd()) {
        QString line = in.readLine().trimmed();
        // 跳过空行和注释#
        if (line.isEmpty() || line.startsWith("#")) {
            continue;
        }
        // 对每一行按照空格分割
        QStringList parts = line.split(" ", Qt::SkipEmptyParts);
        if (parts.isEmpty()) continue;

        // 首个字母，用于判断当前是什么数据类型
        QString keyword = parts[0];

        // 指定要使用的材质材料库名称，如：mtllib material.mtl
        // todo 有可能此处使用了多个材料文件，需要手动拼接材料文件路径
        if (keyword == "mtllib") {
            if (!mtlFilePath.isEmpty()) {
                emit parseModelFileDoing(2,1,tr("开始解析材料信息:")+parts[1]);
                // 加载解析模型材料信息
                parseMtlFile(mtlFilePath);
                // 加载了一个材料文件信号
                emit parseModelFileDoing(2,100,tr("材料信息解析完毕:")+parts[1]);
                emit parseModelFileDoing(3,1,tr("继续解析模型OBJ信息"));
            }
        }
        // 指定后续的面（f 关键字定义的面）要使用的材质
        else if (keyword == "usemtl") {
            // 若有，则使用当前的材料，若没有，则使用default_material
            m_currentMaterial = (parts.size() > 1) ? parts[1] : "default_material";
            // 确保该材质在哈希表中有条目（即使未定义也占位）
            if (!m_materialMap.contains(m_currentMaterial)) {
                Material mat;
                mat.name = m_currentMaterial;
                m_materialMap[m_currentMaterial] = mat;
            }
        } else if (keyword == "v") {
            m_positions.append(QVector3D(parts[1].toFloat(), parts[2].toFloat(), parts[3].toFloat()));
        } else if (keyword == "vt") {
            m_texCoords.append(QVector2D(parts[1].toFloat(), parts[2].toFloat()));
        } else if (keyword == "vn") {
            m_normals.append(QVector3D(parts[1].toFloat(), parts[2].toFloat(), parts[3].toFloat()));
        }
        else if (keyword == "f") {
            QVector<FaceIndex> face;
            for (int i = 1; i < parts.size(); ++i) {
                QStringList indices = parts[i].split("/");
                FaceIndex fi;
                // 顶点位置
                if (indices.size() > 0 && !indices[0].isEmpty()) {
                    bool ok;
                    int idx = indices[0].toInt(&ok);
                    if (!ok) continue; // 解析失败跳过
                    if (idx > 0) {
                        fi.posIndex = idx - 1; // 正数: 从1开始 → 转为0起始
                    } else {
                        fi.posIndex = m_positions.size() + idx; // 负数: 倒数，如 -1 → size - 1
                    }
                } else {
                    fi.posIndex = -1;
                }

                // 纹理坐标
                if (indices.size() > 1 && !indices[1].isEmpty()) {
                    bool ok;
                    int idx = indices[1].toInt(&ok);
                    if (!ok) {
                        fi.texIndex = -1;
                    } else {
                        if (idx > 0) {
                            fi.texIndex = idx - 1;
                        } else {
                            fi.texIndex = m_texCoords.size() + idx;
                        }
                    }
                } else {
                    fi.texIndex = -1;
                }

                // 法线
                if (indices.size() > 2 && !indices[2].isEmpty()) {
                    bool ok;
                    int idx = indices[2].toInt(&ok);
                    if (!ok) {
                        fi.normIndex = -1;
                    } else {
                        if (idx > 0) {
                            fi.normIndex = idx - 1;
                        } else {
                            fi.normIndex = m_normals.size() + idx;
                        }
                    }
                } else {
                    fi.normIndex = -1;
                }

                face.append(fi);
            }

            // 只有非空面才添加
            if (!face.isEmpty()) {
                m_faceGroups[m_currentMaterial].append(face);
            }
        }
    }
    emit parseModelFileDoing(3,100,tr("模型OBJ信息解析完毕"));
    file.close();

    emit parseModelFileDoing(4,0,tr("开始处理面元信息"));
    // 调用分组处理函数来生成 meshParts
    bool handleFaceResult = handleFaceGroups();
    emit parseModelFileDoing(4,100,tr("面元信息处理完毕"));

    // 解析模型完毕信号
    emit parseModelFileCompleted();
    return handleFaceResult;
}


bool ModelFileParse::handleFace(){


    if (m_positions.isEmpty()) {
        qWarning() << "No positions loaded!";
        return false;
    }

    QHash<QString, unsigned int> vertexToIndexMap;

    for (int faceIdx = 0; faceIdx < faces.size(); ++faceIdx) {
        const QVector<FaceIndex>& face = faces[faceIdx];
        if (face.size() < 3) continue; // 至少需要三个顶点构成三角形

        for (int i = 0; i < face.size(); ++i) {
            const FaceIndex& fi = face[i];

            // 检查索引有效性
            if (fi.posIndex < 0 || fi.posIndex >= m_positions.size()) {
                qWarning() << "Invalid position index:" << fi.posIndex << "in face" << faceIdx;
                continue;
            }
            if (fi.normIndex >= 0 && fi.normIndex >= m_normals.size()) {
                qWarning() << "Invalid normal index:" << fi.normIndex;
                continue;
            }
            if (fi.texIndex >= 0 && fi.texIndex >= m_texCoords.size()) {
                qWarning() << "Invalid texture coordinate index:" << fi.texIndex;
                continue;
            }

            QVector3D pos = m_positions[fi.posIndex];
            QVector3D norm = (fi.normIndex >= 0) ? m_normals[fi.normIndex] : QVector3D(0, 0, 1);
            QVector2D tex = (fi.texIndex >= 0) ? m_texCoords[fi.texIndex] : QVector2D(0, 0);

            QString key = QString::asprintf("%.6f_%.6f_%.6f_%.6f_%.6f_%.6f_%.6f_%.6f",
                pos.x(), pos.y(), pos.z(),
                norm.x(), norm.y(), norm.z(),
                tex.x(), tex.y());

            if (vertexToIndexMap.contains(key)) {
                m_indexData.append(vertexToIndexMap[key]);
            } else {
                unsigned int newIndex = vertexToIndexMap.size();
                vertexToIndexMap[key] = newIndex;
                m_indexData.append(newIndex);

                m_vertexData << pos.x() << pos.y() << pos.z()
                             << norm.x() << norm.y() << norm.z()
                             << tex.x() << tex.y();
            }
        }
    }

    qDebug() << "Unique vertices:" << vertexToIndexMap.size();
    qDebug() << "Index count:" << m_indexData.size();

    if (m_indexData.isEmpty()) {
        qWarning() << "Failed to generate valid index data!";
        return false;
    }

    return true;
}


bool ModelFileParse::handleFaceGroups() {
    m_meshParts.clear();

    unsigned int lastPro = 0;
    unsigned int numIndex = 1;
    for (auto it = m_faceGroups.begin(); it != m_faceGroups.end(); ++it,++numIndex) {
        const QString& materialName = it.key();
        const QVector<QVector<FaceIndex>>& faceList = it.value();
        qDebug()<<"材料名称"<<materialName;

        QVector<float> vertexData;
        QVector<unsigned int> indexData;
        QHash<QString, unsigned int> vertexToIndexMap;

        // 顶点数据去重并构建
        for (const auto& face : faceList) {
            if (face.size() < 3) continue;
            unsigned int currentPro = (((double)numIndex)/(m_faceGroups.size()*faceList.size()))*100;
            if(lastPro != currentPro){
                emit parseModelFileDoing(4,currentPro,tr("面元信息处理中"));
                lastPro = currentPro;
            }
            for (int i = 0; i < face.size(); ++i) {
                const FaceIndex& fi = face[i];
                if (fi.posIndex < 0 || fi.posIndex >= m_positions.size()) continue;

                QVector3D pos = m_positions[fi.posIndex];
                QVector3D norm = (fi.normIndex >= 0) ? m_normals[fi.normIndex] : QVector3D(0,0,1);
                QVector2D tex = (fi.texIndex >= 0) ? m_texCoords[fi.texIndex] : QVector2D(0,0);

                QString key = QString::asprintf("%.6f_%.6f_%.6f_%.6f_%.6f_%.6f_%.6f_%.6f",
                    pos.x(), pos.y(), pos.z(),
                    norm.x(), norm.y(), norm.z(),
                    tex.x(), tex.y());

                if (vertexToIndexMap.contains(key)) {
                    indexData.append(vertexToIndexMap[key]);
                } else {
                    unsigned int idx = vertexToIndexMap.size();
                    vertexToIndexMap[key] = idx;
                    indexData.append(idx);

                    vertexData << pos.x() << pos.y() << pos.z()
                               << norm.x() << norm.y() << norm.z()
                               << tex.x() << tex.y();
                }
            }
            ++numIndex;
        }

        if (vertexData.isEmpty() || indexData.isEmpty()) continue;

        // 创建 geometry renderer
        Qt3DRender::QGeometryRenderer* renderer = this->createGeometryRendererFromData(vertexData, indexData);
        if (!renderer) continue;

        // 创建材质：根据是否有纹理选择不同材质
        Qt3DRender::QMaterial* material = nullptr;

        if (m_materialMap.contains(materialName)) {
            const Material& mtl = m_materialMap[materialName];

            if (!mtl.diffuseTexturePath.isEmpty()) {
                QFile texFile(mtl.diffuseTexturePath);
                if (!texFile.exists()) {
                    qWarning() << "Texture not found, using fallback color:" << mtl.diffuseTexturePath;
                    Qt3DExtras::QPhongMaterial* mat = new Qt3DExtras::QPhongMaterial();
                    mat->setDiffuse(QColor::fromRgbF(mtl.diffuse.x(), mtl.diffuse.y(), mtl.diffuse.z()));
                    material = mat;
                } else {
                    Qt3DExtras::QDiffuseMapMaterial* mat = new Qt3DExtras::QDiffuseMapMaterial();

                    Qt3DRender::QTexture2D* texture = new Qt3DRender::QTexture2D();
                    Qt3DRender::QTextureImage* texImg = new Qt3DRender::QTextureImage();
                    texImg->setSource(QUrl::fromLocalFile(mtl.diffuseTexturePath));
                    texture->addTextureImage(texImg);

                    // 必须设置过滤模式
                    texture->setMinificationFilter(Qt3DRender::QAbstractTexture::LinearMipMapLinear);
                    texture->setMagnificationFilter(Qt3DRender::QAbstractTexture::Linear);
                    // 正确设置 Wrap Mode
                    Qt3DRender::QTextureWrapMode wrapMode;
                    wrapMode.setX(Qt3DRender::QTextureWrapMode::Repeat);
                    wrapMode.setY(Qt3DRender::QTextureWrapMode::Repeat);
                    texture->setWrapMode(wrapMode);

                    mat->setDiffuse(texture);
                    mat->setAmbient(QColor::fromRgbF(mtl.ambient.x(), mtl.ambient.y(), mtl.ambient.z()));

                    material = mat;
                }
            }  else {
                // 无纹理 → 使用 QPhongMaterial
                Qt3DExtras::QPhongMaterial* mat = new Qt3DExtras::QPhongMaterial();
                mat->setDiffuse(QColor::fromRgbF(mtl.diffuse.x(), mtl.diffuse.y(), mtl.diffuse.z()));
                mat->setAmbient(QColor::fromRgbF(mtl.ambient.x(), mtl.ambient.y(), mtl.ambient.z()));
                mat->setSpecular(QColor::fromRgbF(mtl.specular.x(), mtl.specular.y(), mtl.specular.z()));
                mat->setShininess(mtl.shininess);

                material = mat;
            }
        } else {
            // 默认材质
            material = new Qt3DExtras::QPhongMaterial();
        }

        m_meshParts.append(MeshPart(renderer, material));

    }

    return !m_meshParts.isEmpty();
}

/**
 * @brief createGeometryRenderer 从解析的数据创建几何体渲染器
 * @return 返回配置好的QGeometryRenderer对象
 */
Qt3DRender::QGeometryRenderer* ModelFileParse::createGeometryRenderer(QObject* parent)
{
    Qt3DCore::QNode *nodeParent = qobject_cast<Qt3DCore::QNode*>(parent);
    if (!nodeParent) {
        qWarning() << "Parent must be a Qt3DCore::QNode or derived class (like QEntity)";
        return nullptr;
    }

    if (m_vertexData.isEmpty()) {
        qWarning() << "m_vertexData is empty! Cannot create geometry.";
        return nullptr;
    }
    if (m_indexData.isEmpty()) {
        qWarning() << "m_indexData is empty! Cannot create indexed drawing.";
        return nullptr;
    }

    Qt3DRender::QGeometry* geometry = new Qt3DRender::QGeometry(nodeParent);

    // 1. 创建顶点缓冲区并设置数据
    Qt3DRender::QBuffer* vertexBuffer = new Qt3DRender::QBuffer(geometry);
    QByteArray vertexByteArray;
    vertexByteArray.resize(m_vertexData.size() * sizeof(float));
    memcpy(vertexByteArray.data(), m_vertexData.constData(), vertexByteArray.size());
    vertexBuffer->setData(vertexByteArray);

    // 2. 创建索引缓冲区（如果使用索引绘制）
//    Qt3DRender::QBuffer* indexBuffer = new Qt3DRender::QBuffer(geometry);
//    QByteArray indexByteArray;
//    indexByteArray.resize(m_indexData.size() * sizeof(unsigned int));
//    memcpy(indexByteArray.data(), m_indexData.constData(), indexByteArray.size());
//    indexBuffer->setData(indexByteArray);

    // 使用 quint32（unsigned int）存储索引
    Qt3DRender::QBuffer* indexBuffer = new Qt3DRender::QBuffer(geometry);
    QByteArray indexByteArray;
    indexByteArray.resize(m_indexData.size() * sizeof(quint32)); // 注意：sizeof(quint32)

    // 直接复制 uint 数据
    memcpy(indexByteArray.data(), m_indexData.constData(), indexByteArray.size());
    indexBuffer->setData(indexByteArray);

    // 3. 创建顶点属性（位置、法线、纹理坐标）
    // 计算每个顶点的总字节数：位置(3) + 法线(3) + 纹理坐标(2) = 8个float
    const int vertexSize = 8;
    const int stride = vertexSize * sizeof(float);

    // 位置属性
    auto* positionAttribute = new Qt3DRender::QAttribute(geometry);
    positionAttribute->setName(Qt3DRender::QAttribute::defaultPositionAttributeName());
    positionAttribute->setVertexBaseType(Qt3DRender::QAttribute::Float);
    positionAttribute->setVertexSize(3); // 3个float表示一个位置
    qDebug()<<"m_vertexData"<<m_vertexData.size() << vertexSize;
    positionAttribute->setCount(m_vertexData.size() / vertexSize);
    positionAttribute->setByteStride(stride);
    positionAttribute->setByteOffset(0);
    positionAttribute->setBuffer(vertexBuffer);
    positionAttribute->setAttributeType(Qt3DRender::QAttribute::VertexAttribute);
    geometry->addAttribute(positionAttribute);

    // 法线属性
    auto* normalAttribute = new Qt3DRender::QAttribute(geometry);
    normalAttribute->setName(Qt3DRender::QAttribute::defaultNormalAttributeName());
    normalAttribute->setVertexBaseType(Qt3DRender::QAttribute::Float);
    normalAttribute->setVertexSize(3);
    normalAttribute->setCount(m_vertexData.size() / vertexSize);
    normalAttribute->setByteStride(stride);
    normalAttribute->setByteOffset(3 * sizeof(float)); // 法线在位置之后（3个float）
    normalAttribute->setBuffer(vertexBuffer);
    normalAttribute->setAttributeType(Qt3DRender::QAttribute::VertexAttribute);
    geometry->addAttribute(normalAttribute);

    // 纹理坐标属性
    auto* texCoordAttribute = new Qt3DRender::QAttribute(geometry);
    texCoordAttribute->setName(Qt3DRender::QAttribute::defaultTextureCoordinateAttributeName());
    texCoordAttribute->setVertexBaseType(Qt3DRender::QAttribute::Float);
    texCoordAttribute->setVertexSize(2);
    texCoordAttribute->setCount(m_vertexData.size() / vertexSize);
    texCoordAttribute->setByteStride(stride);
    texCoordAttribute->setByteOffset(6 * sizeof(float)); // 纹理坐标在法线之后（6个float）
    texCoordAttribute->setBuffer(vertexBuffer);
    texCoordAttribute->setAttributeType(Qt3DRender::QAttribute::VertexAttribute);
    geometry->addAttribute(texCoordAttribute);

    // 4. 创建索引属性（如果使用索引绘制）
    auto* indexAttribute = new Qt3DRender::QAttribute(geometry);
//    indexAttribute->setVertexBaseType(Qt3DRender::QAttribute::UnsignedInt);
    // 设置 attribute 为 UnsignedShort
    indexAttribute->setVertexBaseType(Qt3DRender::QAttribute::UnsignedInt);

    indexAttribute->setCount(m_indexData.size());
    indexAttribute->setBuffer(indexBuffer);
    indexAttribute->setAttributeType(Qt3DRender::QAttribute::IndexAttribute);

    geometry->addAttribute(indexAttribute);

    // 5. 创建几何体渲染器
    Qt3DRender::QGeometryRenderer* geometryRenderer = new Qt3DRender::QGeometryRenderer();
    geometryRenderer->setGeometry(geometry);
    geometryRenderer->setPrimitiveType(Qt3DRender::QGeometryRenderer::Triangles); // 绘制三角形

    return geometryRenderer;
}

Qt3DRender::QGeometryRenderer* ModelFileParse::createGeometryRendererFromData(
    const QVector<float>& vertexData,
    const QVector<unsigned int>& indexData)
{
    Qt3DRender::QGeometry* geometry = new Qt3DRender::QGeometry();

    // Vertex Buffer
    Qt3DRender::QBuffer* vertexBuffer = new Qt3DRender::QBuffer(geometry);
    QByteArray vData;
    vData.resize(vertexData.size() * sizeof(float));
    memcpy(vData.data(), vertexData.constData(), vData.size());
    vertexBuffer->setData(vData);

    // Index Buffer
    Qt3DRender::QBuffer* indexBuffer = new Qt3DRender::QBuffer(geometry);
    QByteArray iData;
    iData.resize(indexData.size() * sizeof(quint32));
    memcpy(iData.data(), indexData.constData(), iData.size());
    indexBuffer->setData(iData);

    // Attributes (same as before)
    const int stride = 8 * sizeof(float);

    auto addAttribute = [&](const QString& name, int size, int offset) {
        auto* attr = new Qt3DRender::QAttribute(geometry);
        attr->setName(name);
        attr->setVertexBaseType(Qt3DRender::QAttribute::Float);
        attr->setVertexSize(size);
        attr->setByteStride(stride);
        attr->setByteOffset(offset * sizeof(float));
        attr->setBuffer(vertexBuffer);
        attr->setAttributeType(Qt3DRender::QAttribute::VertexAttribute);
        attr->setCount(vertexData.size() / 8);
        geometry->addAttribute(attr);
    };

    addAttribute(Qt3DRender::QAttribute::defaultPositionAttributeName(), 3, 0);
    addAttribute(Qt3DRender::QAttribute::defaultNormalAttributeName(),     3, 3);
    addAttribute(Qt3DRender::QAttribute::defaultTextureCoordinateAttributeName(), 2, 6);

    // Index attribute
    auto* indexAttr = new Qt3DRender::QAttribute(geometry);
    indexAttr->setVertexBaseType(Qt3DRender::QAttribute::UnsignedInt);
    indexAttr->setAttributeType(Qt3DRender::QAttribute::IndexAttribute);
    indexAttr->setBuffer(indexBuffer);
    indexAttr->setCount(indexData.size());
    geometry->addAttribute(indexAttr);

    // Renderer
    Qt3DRender::QGeometryRenderer* renderer = new Qt3DRender::QGeometryRenderer();
    renderer->setGeometry(geometry);
    renderer->setPrimitiveType(Qt3DRender::QGeometryRenderer::Triangles);
    return renderer;
}


QVector3D ModelFileParse::getModelCenter() const
{
    if (m_positions.isEmpty())
        return QVector3D(0, 0, 0);

    QVector3D minPoint( std::numeric_limits<float>::max(),
                        std::numeric_limits<float>::max(),
                        std::numeric_limits<float>::max());
    QVector3D maxPoint(-std::numeric_limits<float>::max(),
                       -std::numeric_limits<float>::max(),
                       -std::numeric_limits<float>::max());

    for (const QVector3D& v : m_positions) {
        minPoint.setX(qMin(minPoint.x(), v.x()));
        minPoint.setY(qMin(minPoint.y(), v.y()));
        minPoint.setZ(qMin(minPoint.z(), v.z()));

        maxPoint.setX(qMax(maxPoint.x(), v.x()));
        maxPoint.setY(qMax(maxPoint.y(), v.y()));
        maxPoint.setZ(qMax(maxPoint.z(), v.z()));
    }

    return 0.5f * (minPoint + maxPoint); // 包围盒中心
}

bool ModelFileParse::parseMtlFile(const QString& mtlPath) {
    QFile file(mtlPath);
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        qWarning() << "Cannot open MTL file:" << mtlPath;
        return false;
    }

    Material currentMaterial;

    QTextStream in(&file);
    while (!in.atEnd()) {
        QString line = in.readLine().trimmed();
        if (line.isEmpty() || line.startsWith("#")) continue;

        QStringList parts = line.split(" ", Qt::SkipEmptyParts);
        if (parts.isEmpty()) continue;

        QString key = parts[0];

        if (key == "newmtl" && parts.size() > 1) {
            // 保存上一个材质
            if (!currentMaterial.name.isEmpty()) {
                m_materialMap[currentMaterial.name] = currentMaterial;
            }
            currentMaterial = Material();
            currentMaterial.name = parts[1];
        }
        else if (key == "Ka" && parts.size() >= 4) {
            currentMaterial.ambient = QVector3D(parts[1].toFloat(), parts[2].toFloat(), parts[3].toFloat());
        }
        else if (key == "Kd" && parts.size() >= 4) {
            currentMaterial.diffuse = QVector3D(parts[1].toFloat(), parts[2].toFloat(), parts[3].toFloat());
        }
        else if (key == "Ks" && parts.size() >= 4) {
            currentMaterial.specular = QVector3D(parts[1].toFloat(), parts[2].toFloat(), parts[3].toFloat());
        }
        else if (key == "Ns" && parts.size() >= 2) {
            currentMaterial.shininess = parts[1].toFloat();
        }
        else if (key == "map_Kd" && parts.size() >= 2) {
            QString texPath = parts.mid(1).join(" ").trimmed();
            // 处理相对路径
            QFileInfo mtlInfo(mtlPath);
            currentMaterial.diffuseTexturePath = mtlInfo.dir().filePath(texPath);
        }
    }

    // 保存最后一个材质
    if (!currentMaterial.name.isEmpty()) {
        m_materialMap[currentMaterial.name] = currentMaterial;
    }

    qDebug() << "Loaded" << m_materialMap.size() << "materials from" << mtlPath;
    return true;
}

